export class FocusTracker {
    mount(win: Window) {
      const checkDown = (e: MouseEvent) => {
        if (this.checkModalDown(e)) {
          return;
        }
        const { first } = this;
        if (first && !first.internalCheckInRange(e)) {
          this.internalSuspenseItem(first);
          first.internalTriggerBlur();
        }
      };
      win.document.addEventListener('click', checkDown, true);
      return () => {
        win.document.removeEventListener('click', checkDown, true);
      };
    }

    private actives: Focusable[] = [];

    get first() {
      return this.actives[0];
    }

    private modals: Array<{ checkDown: (e: MouseEvent) => boolean; checkOpen: () => boolean }> = [];

    addModal(checkDown: (e: MouseEvent) => boolean, checkOpen: () => boolean) {
      this.modals.push({
        checkDown,
        checkOpen,
      });
    }

    private checkModalOpen(): boolean {
      return this.modals.some((item) => item.checkOpen());
    }

    private checkModalDown(e: MouseEvent): boolean {
      return this.modals.some((item) => item.checkDown(e));
    }

    execSave() {
      // has Modal return;
      if (this.checkModalOpen()) {
        return;
      }
      // catch
      if (this.first) {
        this.first.internalTriggerSave();
      }
    }

    execEsc() {
      const { first } = this;
      if (first) {
        this.internalSuspenseItem(first);
        first.internalTriggerEsc();
      }
    }

    create(config: FocusableConfig) {
      return new Focusable(this, config);
    }

    internalActiveItem(item: Focusable) {
      const first = this.actives[0];
      if (first === item) {
        return;
      }
      const i = this.actives.indexOf(item);
      if (i > -1) {
        this.actives.splice(i, 1);
      }
      this.actives.unshift(item);
      if (!item.isModal && first) {
        // trigger Blur
        first.internalTriggerBlur();
      }
      // trigger onActive
      item.internalTriggerActive();
    }

    internalSuspenseItem(item: Focusable) {
      const i = this.actives.indexOf(item);
      if (i > -1) {
        this.actives.splice(i, 1);
        this.first?.internalTriggerActive();
      }
    }
  }

  export interface FocusableConfig {
    range: HTMLElement | ((e: MouseEvent) => boolean);
    modal?: boolean; // 模态窗口级别
    onEsc?: () => void;
    onBlur?: () => void;
    onSave?: () => void;
    onActive?: () => void;
  }

  export class Focusable {
    readonly isModal: boolean;

    constructor(private tracker: FocusTracker, private config: FocusableConfig) {
      this.isModal = config.modal == null ? false : config.modal;
    }

    active() {
      this.tracker.internalActiveItem(this);
    }

    suspense() {
      this.tracker.internalSuspenseItem(this);
    }

    purge() {
      this.tracker.internalSuspenseItem(this);
    }

    internalCheckInRange(e: MouseEvent) {
      const { range } = this.config;
      if (!range) {
        return false;
      }
      if (typeof range === 'function') {
        return range(e);
      }
      return range.contains(e.target as HTMLElement);
    }

    internalTriggerBlur() {
      if (this.config.onBlur) {
        this.config.onBlur();
      }
    }

    internalTriggerSave() {
      if (this.config.onSave) {
        this.config.onSave();
        return true;
      }
      return false;
    }

    internalTriggerEsc() {
      if (this.config.onEsc) {
        this.config.onEsc();
      }
    }

    internalTriggerActive() {
      if (this.config.onActive) {
        this.config.onActive();
      }
    }
  }

  export const focusTracker = new FocusTracker();

  focusTracker.mount(window);
